<?php
/********************************************************************************/
/* wordCloudCombo -- Lisa Miller 8/2009  					*/
/* This class makes a cloud with data from more than one source or "name." 	*/
/* Each word is stored as an array element with its stem as the key pointing 	*/
/* to an array containing the word and arrays for the data for that word as  	*/
/* it pertains to each "name."                                               	*/
/*										*/
/* The intention is to display the cloud with each word displayed several times */
/* right next to each other, once for each "name" with each "name" being      	*/
/* assigned a different display color and the size of the word reflecting the  	*/
/* number of times the word was associated with that particular "name"		*/
/*										*/
/* Colors need to be manually assigned from outside the class, this could 	*/
/* probably be improved to happen automatically within the class at the time    */
/* the cloud is returned.                                                       */
/********************************************************************************/

	require_once("class.stemmer.inc");

	class wordCloudCombo
	{
		private $wordsArray;
		private $stemmer;
		private $removes;
		private $countLimit;
		private $wordLimit;
		private $maxArray;

		public function __construct()
		{
		     $this->wordsArray = array();
		     $this->removes = array();
		     $this->stemmer = new Stemmer();
		     $this->countLimit = NULL;
		     $this->wordLimit = NULL;
		     $this->maxArray = array();

		}

	       public function __get($name)
	       {
		    return $this->$name;
	       }

	       public function __set($attribute, $value)
	       {
		    $this->$attribute = $value;
	       }

	       /* Adds a wordCloud associative array to the combined cloud    */
	       /* Each added cloud must have a unique name associated with it */
	       public function addCloud($newCloud, $name)
	       {
		    $name = trim($name);
		    foreach($newCloud as $stem => $data)
		    {

			//if word isn't in the cloud yet add it
			if (empty($this->wordsArray[$stem])) {
				$this->wordsArray[$stem] = array();
				$this->wordsArray[$stem]['total_count'] = 0;
			}
			//check to see if saved word is shortest
			//keep shortest version
			if(!empty($this->wordsArray[$stem]['word']))
			{
			     if(strlen($data['word']) < strlen($this->wordsArray[$stem]['word']))
			     {
				  //replace the current saved word with a shorter one
				  $this->wordsArray[$stem]['word'] = $data['word'];
			     }
			}else{
			     $this->wordsArray[$stem]['word'] = $data['word'];
			}
			//echo 'Added: '.$this->wordsArray[$stem]['word'].'<br />';
			//add data for this name/cloud
			$this->wordsArray[$stem][$name] = array();
			$this->wordsArray[$stem][$name]['count'] = $data['count'];
			$this->wordsArray[$stem][$name]['url'] = $data['url'];
			$this->wordsArray[$stem][$name]['color'] = $data['color'];
			$this->wordsArray[$stem][$name]['range'] = 0;

			$this->wordsArray[$stem]['total_count'] += $data['count'];
		    }

		}


		/* Sets a lower limit for the count of */
		/* words included in cloud.            */
		/* ie: if set to 3, only words seen 3  */
		/* or more times will be included in   */
		/* the cloud array                     */
		public function setCountLimit($limit)
		{
			$this->countLimit = $limit;
		}

		/* Sets a limit on the number of words */
		/* included in the cloud.              */
		/* only the $limit number of words with*/
		/* the highest seen counts will be incl.*/
		public function setWordLimit($limit)
		{
		     $this->wordLimit = $limit;
		}

	       //adds word to array to be taken out of wordCloud
		public function removeWord($word)
		{
			$this->removes[] = strtolower($word);
		}


		/* returns Cloud with word sizes normalized between names */
		/* for if one name's words have much higher counts than   */
		/* another's and you want to just compare which words are */
		/* more significant for one name relative to the other(s) */
		public function getCloud()
		{

			if (!empty($this->countLimit))
			{
				//echo 'Calling limitCounts<br />';
				$this->limitCounts();
			}
			if (!empty($this->removes))
			{
				//echo 'Calling removeWords <br />';
				$this->removeWords();
			}
			if (!empty($this->wordLimit))
			{
			     //echo 'Calling limitWords<br />';
			     $this->limitWords();
			}

			$this->shuffleCloud();


			$this->scaleCounts();

			$this->partitionRanges();

			return $this->wordsArray;
		}

		//******************* PRIVATE FUNCTIONS ***********************************

	       /* Partition counts into 10 size ranges */
	       private function partitionRanges()
	       {
		    $maximum = $this->getMax();
		    $minimum = $this->getMin();
// 		    echo 'Max = '.$maximum.' Min = '.$minimum.'<br />';
		    $increment = ($maximum - $minimum)/9;

		    foreach ($this->wordsArray as $stem => $names)
		    {
			 foreach($names as $name => $data)
			 {
			      if(is_array($data)) {
					$sizeRange = (int)(($data['count']-$minimum) / $increment);

					//echo 'count = '.$data['count'].' size = '.$sizeRange.'<br />';
					$this->wordsArray[$stem][$name]['range'] = $sizeRange;

			      }
			 }

		    }

	       }

		/* Scales word counts between 'names'  */
		/* so that max count for each name is  */
		/* the same.                           */
		/* Ensures that importance of word     */
		/* is what determines size rather than */
		/* raw search result counts            */
	       private function scaleCounts()
	       {
		    $this->maxArray = $this->getMaxArray();
		    $scaleArray = array();
		    foreach($this->maxArray as $n => $m)
		    {
			 $scaleArray[$n] = $this->maxArray['overall_max']/$this->maxArray[$n];
			 //echo 'for '.$n.' scaling = '.$scaleArray[$n]. ' = '. $this->maxArray['overall_max'].' / '.$this->maxArray[$n].'<br />';

		    }
		    foreach($this->wordsArray as $stem => $names)
		    {
			 foreach($names as $name => $data)
			 {
			       if(is_array($data))
			       {

				   $this->wordsArray[$stem][$name]['count'] = $data['count'] * $scaleArray[$name];

			       }
			 }
		    }
	       }

		/*
		 * Shuffle associated names in array
		 *
		 * @return array $this->wordsArray
		 */

		private function shuffleCloud()
		{
			$stems = array_keys($this->wordsArray);
			shuffle($stems);
			if (count($stems) && is_array($stems)) {
				$tmpArray = $this->wordsArray;
				$this->wordsArray = array();
				foreach ($stems as $stem)
					$this->wordsArray[$stem] = $tmpArray[$stem];
			}
			return $this->wordsArray;
		}

		/*
		 * Get the class range using a percentage
		 *
		 * @returns int $class
		 */

		private function getClassFromPercent($percent)
		{
			if ($percent >= 99)
				$class = 9;
			elseif ($percent >= 70)
				$class = 8;
			elseif ($percent >= 60)
				$class = 7;
			elseif ($percent >= 50)
				$class = 6;
			elseif ($percent >= 40)
				$class = 5;
			elseif ($percent >= 30)
				$class = 4;
			elseif ($percent >= 20)
				$class = 3;
			elseif ($percent >= 10)
				$class = 2;
			elseif ($percent >= 5)
				$class = 1;
			else
				$class = 0;
			return $class;
		}
		/*
		 * Finds the maximum count of any word for a particular name
		 */

		private function getMaxArray()
		{
			$maxArray = array();
			$maxArray['overall_max'] = 0;
			 foreach ($this->wordsArray as $stem => $data)
			 {
			      foreach($data as $n => $d)
			      {
				   if(is_array($d)){
					if ($d['count'] > $maxArray['overall_max'])
					{
					     $maxArray['overall_max'] = $d['count'];
					}
					if($d['count'] > $maxArray[$n])
					{
					     $maxArray[$n] = $d['count'];
					}
				   }
			      }
			}
			return $maxArray;
		}


		/*
		 * Finds the maximum count
		 */

		private function getMax()
		{
			$max = 0;

			 foreach ($this->wordsArray as $stem => $data)
			 {
			      foreach($data as $d)
			      {
				   if ($d['count'] > $max)
				   {
					$max = $d['count'];
				   }
			      }
			}
			return $max;
		}
		/*
		 * Finds the minimum count
		 */

		private function getMin()
		{
			$min = PHP_INT_MAX;

			 foreach ($this->wordsArray as $stem => $data)
			 {
			      foreach($data as $d)
			      {
				   if ($d['count'] < $min)
				   {
					$min = $d['count'];
				   }
			      }
			}
			return $min;
		}




		//removes the list of removes from the cloud
		private function removeWords()
		{
			foreach ($this->removes as $badWord) {

			      $badStem = (string)$this->stemmer->stem($badWord);
			      if (array_key_exists($badStem, $this->wordsArray))
			      {
				   unset($this->wordsArray[$badStem]);
			      }
			}
		}




		/*
		 * Removes words for names with count less than limit value
		 */
		private function limitCounts()
		{
	;
			foreach ($this->wordsArray as $stem => $data)
			{
			      foreach($data as $name => $info)
			      {
				   //if this is not the word data look at the count
				   if (strcmp($name,'word') && ($info['count'] < $this->limitAmount))
				   {
					$this->wordsArray[$stem]['total_count'] -= $info['count'];
					//unset($name);
					unset($this->wordsArray[$stem][$name]);
				   }
			      }
			      if($this->wordsArray[$stem]['total_count'] < 1)
			      {
				   unset($this->wordsArray[$stem]);
			      }

			}
		}


		private function limitWords()
		{
		    $tempArray = $this->wordsArray;
		    //sort by word "total_count"
		    uasort($tempArray, 'countCmp');
		    //chop off all but the top count words
		    $output = array_slice($tempArray, 0, $this->wordLimit, true);
		    //take everything else out of the array (maintains order)
		    $this->wordsArray = $output;
		    unset($tempArray);
		    unset($output);

		}
	}//end wordCloudCombo class


	       //non-class function for sorting associative array by stem=>total_count
	       //using PHP uasort in limitWords method
	       function countCmp($a , $b)
	       {
		    if ($a['total_count'] == $b['total_count']) {
			      return 0;
		    }
			 return ($a['total_count'] < $b['total_count']) ? 1 : -1;

	       }

?>